package org.lab.mtga.core.simulator;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.lab.mtga.core.exception.InvalidDeckMode;
import org.lab.mtga.core.model.Card;
import org.lab.mtga.core.model.CardType;
import org.lab.mtga.core.model.Deck;
import org.lab.mtga.core.parser.DeckParser;
import org.lab.mtga.core.services.RandomService;

public class HandSimulator {

	@Inject
	private DeckParser deckParser;
	@Inject
	private RandomService randomService;

	public void initalize(InputStream inputStream) throws IOException {
		Deck deck = deckParser.parse(inputStream);
		deck.initialize();
		displayCurrentCardList(deck);
		System.out.println("Type help to see available options");
		while (true) {
			try {
				System.out.print("option$ ");
				String option = readLine();
				switch (option) {
				case "help":
					printOptions();
					break;
				case "test":
					testHand(deck, 7);
					break;
				case "modes":
					for (String i : deck.getModes().keySet()) {
						System.out.println("   " + i);
					}
					break;
				default:
					if (option.startsWith("mode ")) {
						try {
							String modeName = option.substring(5);
							deck.initialize(modeName);
							Map<Card, Integer> modeConfiguration = deck.getModes().get(modeName);
							for (Card card : modeConfiguration.keySet()) {
								System.out.println(String.format("   %3s  %s", modeConfiguration.get(card), card.getName()));
							}
							displayCurrentCardList(deck);
						} catch (InvalidDeckMode ex) {
							System.out.println("Invalid mode" + ex.getName());
						}
					} else {
						System.out.println(option + " is not valid option. Type help to see available commands");
					}
				}
			} catch (InvalidDeckMode ex) {
				System.out.println("Invalid mode " + ex.getName());
			}
		}
	}

	private void displayCurrentCardList(Deck deck) {
		printFormatted("Deck:", deck.getCardList());
		System.out.println("Type distribution:");
		System.out.println("   Lands        : " + deck.getCardCount(CardType.Land));
		System.out.println("   Creatures    : " + deck.getCardCount(CardType.Creature));
		System.out.println("   Instants     : " + deck.getCardCount(CardType.Instant));
		System.out.println("   Sorceries    : " + deck.getCardCount(CardType.Sorcery));
		System.out.println("   Artifacts    : " + deck.getCardCount(CardType.Artifact));
		System.out.println("   Planeswalkers: " + deck.getCardCount(CardType.Planeswalker));
		System.out.println("   Total        : " + deck.getCardList().size());
		Map<Integer, Integer> cmcDistribution = new LinkedHashMap<>();
		for (int i = 0; i < 15; i++) {
			cmcDistribution.put(i, 0);
		}
		int totalCmc = 0;
		for (Card i : deck.getCardList()) {
			Integer cmcValue = i.getCmcCount() != null ? i.getCmcCount() : 0;
			totalCmc += cmcValue;
			if (cmcDistribution.containsKey(cmcValue)) {
				cmcDistribution.put(cmcValue, cmcDistribution.get(cmcValue) + 1);
			} else {
				cmcDistribution.put(cmcValue, 1);
			}
		}
		System.out.println("CMC Average     : " + new BigDecimal(totalCmc).divide(new BigDecimal(deck.getCardList().size()), 2, RoundingMode.HALF_EVEN));
		System.out.println("CMC distribution:");
		for (Integer cmc : cmcDistribution.keySet()) {
			if (cmcDistribution.get(cmc) > 0) {
				System.out.println(String.format("   %-2s: %-2s", cmc, cmcDistribution.get(cmc)));
			}
		}
	}

	private void printOptions() {
		System.out.println("  test          start hand simulation test");
		System.out.println("  load ${mode}  load ");
		System.out.println("  modes         display available deck modes");
	}

	private void testHand(Deck deck, int handSize) throws IOException {
		List<Card> list = deck.getCardList();
		if (handSize == 0) {
			System.out.println("Empty initial hand");
			printList("Next draws:", list, 60);
			return;
		}
		randomService.randomize(list);
		List<Card> initialHand = list.subList(0, handSize);
		printFormatted("Initial Hand (" + handSize + ")", initialHand);
		String option = null;
		while (option == null || (!"y".equals(option) && !"n".equals(option))) {
			System.out.print("Keep hand? [y/n]: ");
			option = readLine();
		}
		if (option.equals("y")) {
			List<Card> nextDraws = list.subList(handSize, list.size() - 1);
			printList("Next draws:", nextDraws, 20);
		} else if (option.equals("n")) {
			handSize--;
			System.out.println("Mulligan to " + handSize);
			testHand(deck, handSize);
		}
	}

	private String readLine() throws IOException {
		return new BufferedReader(new InputStreamReader(System.in)).readLine();
	}

	private void printFormatted(String title, List<Card> list) {
		LinkedHashMap<Card, Integer> lands = new LinkedHashMap<Card, Integer>();
		LinkedHashMap<Card, Integer> creatures = new LinkedHashMap<Card, Integer>();
		LinkedHashMap<Card, Integer> other = new LinkedHashMap<Card, Integer>();
		LinkedHashMap<Card, Integer> instants = new LinkedHashMap<Card, Integer>();
		LinkedHashMap<Card, Integer> sorceries = new LinkedHashMap<Card, Integer>();
		for (Card i : list) {
			if (i.matchesType(CardType.Land)) {
				addCard(lands, i);
			} else if (i.matchesType(CardType.Creature)) {
				addCard(creatures, i);
			} else if (i.matchesType(CardType.Instant)) {
				addCard(instants, i);
			} else if (i.matchesType(CardType.Sorcery)) {
				addCard(sorceries, i);
			} else {
				addCard(other, i);
			}
		}
		System.out.println(title);
		printListType("Lands", lands);
		printListType("Creatures", creatures);
		printListType("Instants", instants);
		printListType("Sorceries", sorceries);
		printListType("Other", other);
	}

	private void addCard(Map<Card, Integer> map, Card card) {
		if (map.containsKey(card)) {
			map.put(card, map.get(card) + 1);
		} else {
			map.put(card, 1);
		}
	}

	private void printListType(String title, Map<Card, Integer> cards) {
		if (!cards.isEmpty()) {
			Integer total = 0;
			for (Integer i : cards.values()) {
				total += i;
			}
			StringBuffer sb = new StringBuffer(String.format("   %-10s (%2s):", title, total));
			for (Iterator<Card> iterator = cards.keySet().iterator(); iterator.hasNext();) {
				Card card = iterator.next();
				Integer count = cards.get(card);
				sb.append(" ");
				sb.append(card.getName());
				if (count > 1) {
					sb.append(" (").append(cards.get(card)).append(")");
				}
				if (iterator.hasNext()) {
					sb.append(", ");
				}
			}
			System.out.println(sb.toString());
		}
	}

	private void printList(String title, List<Card> list, int count) {
		System.out.println(title);
		int half = count / 2;
		for (int i = 0; i < half; i++) {
			Card c0 = list.get(i);
			Card c1 = list.get(half + i);
			String t0 = Arrays.toString(c0.getTypes().toArray());
			String t1 = Arrays.toString(c1.getTypes().toArray());
			System.out.println(String.format("    %2s %-30s %20s       %2s %-30s %20s", i + 1, c0.getName(), t0, i + half + 1, c1.getName(), t1));
		}
	}
}
